/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ar.edu.unicen.twitter.suggest.backend.recommendation.crawling;

import ar.edu.unicen.twitter.suggest.backend.recommendation.CompararUsuarios.CompararPorOcurrencias;
import ar.edu.unicen.twitter.suggest.backend.recommendation.CompararUsuarios.CompararPorScore;
import ar.edu.unicen.twitter.suggest.backend.recommendation.CompararUsuarios.CompararUsuarios;
import ar.edu.unicen.twitter.suggest.backend.recommendation.CriterioUsuario.CriterioUsuario;
import ar.edu.unicen.twitter.suggest.backend.recommendation.crawling.DescartadorUsuarios;
import ar.edu.unicen.twitter.suggest.commons.dtos.Usuario;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ProgressMonitor;

/**
 *
 * @author usuario
 */
public abstract class NivelRecoleccion {

    private final static Logger LOGGER = Logger.getLogger(NivelRecoleccion.class.getName());
    private int MAX_USUARIOS_NIVEL = 2000000;
    private CriterioUsuario criterio;
    private CompararUsuarios orden;
    private int maxNivel;
    private List<Usuario> descartados;
    public int evaluados = 0;

    public List<Usuario> getDescartados() {
        return descartados;
    }
    private List<Usuario> crudo;

    public List<Usuario> getCrudo() {
        return crudo;
    }
    private List<Usuario> resultado;

    public List<Usuario> getResultado() {
        return resultado;
    }
    DescartadorUsuarios descartador = null;

    public void setDescartador(DescartadorUsuarios descartador) {
        this.descartador = descartador;
    }

    public NivelRecoleccion(CriterioUsuario criterio2,
            CompararUsuarios comparador,
            int maxNivel2) {
        criterio = criterio2;
        orden = comparador;
        maxNivel = maxNivel2;
    }

    public List<Usuario> recolectar(List<Usuario> origen, HashSet<Usuario> visitados) {

        HashMap<Long, Usuario> usuariosPosibles = new HashMap<Long, Usuario>();
        HashMap<Long, Usuario> usuarios = new HashMap<Long, Usuario>();
        descartados = new ArrayList<Usuario>();
        for (int i = 0; i < origen.size(); i++) {
            Usuario usuario = origen.get(i);
            LOGGER.log(Level.INFO, "Recolectando {0}/{1}", new Object[]{i, origen.size()});
            if (usuario.getId() == 0 || usuario.isProtected()) {
                continue;
            }
            if (!visitados.contains(usuario)) {
                visitados.add(usuario);
                Collection<Usuario> siguientes = getSiguientes(usuario);
                if (descartador != null) {
                    descartados.addAll(descartador.descartar(siguientes));
                }
                for (Iterator<Usuario> usIt = siguientes.iterator(); usIt.hasNext();) {
                    Usuario siguiente = usIt.next();
                    if (usuario.getId() == 0 || usuario.isProtected()) {
                        visitados.add(usuario);
                        continue;
                    }
                    if (!visitados.contains(siguiente)) {

                        double pesoPropagar = getPesoArco(usuario,siguiente);
                        int mencionesPropagar = usuario.getMentions();
                        
                        if (!usuariosPosibles.containsKey(siguiente.getId())) {
                            siguiente.setScore(pesoPropagar);
                            siguiente.setMentions(mencionesPropagar);
                            usuariosPosibles.put(siguiente.getId(), siguiente);
                        } else {
                            if (!usuarios.containsKey(siguiente.getId())) {
                                siguiente = usuariosPosibles.get(siguiente.getId());
                                usuarios.put(siguiente.getId(), siguiente);
                            } else {
                                siguiente = usuarios.get(siguiente.getId());
                            }
                            siguiente.setScore(siguiente.getScore() + pesoPropagar);
                            siguiente.setMentions(siguiente.getMentions() + mencionesPropagar);

                        }
                    }
                    //si son demasiados, compacta
                    if (origen.size() > 1 && usuarios.size() > MAX_USUARIOS_NIVEL) {
                        LOGGER.log(Level.INFO, "compactando: {0}...", usuarios.size());
                        ArrayList<Usuario> resultadoActual = new ArrayList<Usuario>(usuarios.values());
                        Collections.sort(resultadoActual, new CompararPorOcurrencias());
                        int total = resultadoActual.size();
                        int restantes = total - (total / 2);
                        for (int r = total - 1; r >= restantes; --r) {
                            usuarios.remove(resultadoActual.get(r).getId());
                            ++evaluados;
                        }
                        LOGGER.log(Level.INFO, "compactado: {0}", usuarios.size());
                    }
                }
            }
        }
        evaluados += usuariosPosibles.size();
        if (!usuarios.isEmpty()) {
            resultado = new ArrayList<Usuario>(usuarios.values());
        } else {
            resultado = new ArrayList<Usuario>(usuariosPosibles.values());
        }
        //Elimina todos los nodos con una sola ocurrencia       
        if (criterio != null) {
            //elimina los nodos con bajo puntaje
            Collections.sort(resultado, new CompararPorScore());
            if (origen.size() > 1) {
                if (!resultado.isEmpty()) {
                    int minSize = Math.min(resultado.size() / 2, 20000);
                    resultado.subList(minSize, resultado.size()).clear();
                }
            }

            //elimina los nodos que no cumplen
            Collections.sort(resultado);
            verificar(resultado);
            for (int i = resultado.size() - 1; i >= 0; --i) {
                Usuario usuario = resultado.get(i);
                if (!criterio.cumple(usuario)) {
                    Usuario eliminado = resultado.remove(i);
                    System.out.println("-" + eliminado);
                }
            }
        }
        //ordena
        Collections.sort(resultado, orden);
        crudo = resultado;
        //normaliza
        double normalizador = 1;
        int crudoS = crudo.size();
        for (int i = 0; i < crudoS; i++) {
            Usuario actual = crudo.get(i);
            if (i == 0) {
                normalizador = actual.getScore();
            }
            actual.setScore(actual.getScore() / normalizador);
        }
        //descarta los de menor puntaje
        int resultadoS = Math.min(maxNivel, crudoS);
        if (maxNivel > 0) {
            resultado = new ArrayList<Usuario>();
            for (int i = 0; i < resultadoS; i++) {
                Usuario actual = crudo.get(i);
                resultado.add(actual);
            }
        }
        return resultado;
    }

    public abstract Collection<Usuario> getSiguientes(Usuario u);

    public abstract Collection<Usuario> verificar(Collection<Usuario> usuarios);

    protected abstract double getPesoArco(Usuario origen,Usuario destino);
}
